/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "MMKVPredef.h"
#include <Hilog/log.h>
#include "napi/native_api.h"
#include "MMBuffer.h"
#include "MMKV.h"
#include "MMKVLog.h"
#include "MemoryFile.h"
#include <cstdint>
#include <string>

using namespace std;
using namespace mmkv;

#define  LOG(buf)  OH_LOG_Print(LOG_APP, LOG_DEBUG, 0xAAABB, "mmkv", "%{public}s", buf)
#define  LOG_CUSTOM(type, buf)  OH_LOG_Print(LOG_APP, LOG_DEBUG, 0xAAABB, "mmkv", type, buf)

napi_ref jsFunChangedByOuterProcess;
napi_ref jsFunMMKVLogImp;
napi_ref jsFunMMKVCRCCheckFail;
napi_ref jsFunMMKVFileLengthError;
napi_env napiEnv;
const int POSITION_TWO = 2;
const int POSITION_THREE = 3;

string getStringFromNapi(napi_env env, napi_value arg)
{
    char charStr[1024] = { 0 };
    size_t charLen = 0;
    int len = 1024;
    napi_get_value_string_utf8(env, arg, charStr, len, &charLen);
    string str = charStr;
    return str;
}

vector < string > getVectorFromNapi(napi_env env, napi_value arg)
{
    uint32_t size;
    napi_get_array_length(env, arg, &size);
    vector < string > value;
    for (int i = 0; i < size; i++) {
        napi_value e;
        napi_get_element(env, arg, i, &e);
        char tempChar[128] = { 0 };
        size_t tempCharLen = 0;
        int len = 127;
        napi_get_value_string_utf8(env, e, tempChar, len, &tempCharLen);
        string temp = tempChar;
        value.push_back(temp);
    }
    return value;
}

napi_value createNapiArray(napi_env env, vector < string > value)
{
    napi_value array;
    napi_create_array(env, &array);
    for (int i = 0; i < value.size(); i++) {
        napi_value napi_str;
        char* str = (char*)value[i].c_str();
        napi_create_string_utf8(env, str, value[i].length(), &napi_str);
        napi_set_element(env, array, i, napi_str);
    }
    return array;
}

napi_value createNapiString(napi_env env, string str)
{
    napi_value result;
    char* strChar = (char*)str.c_str();
    napi_create_string_utf8(env, strChar, str.length(), &result);
    return result;
}

napi_value createNapiDouble(napi_env env, double value)
{
    napi_value result;
    napi_create_double(env, value, &result);
    return result;
}

napi_value createNapiUndefined(napi_env env)
{
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

napi_value createNapiNull(napi_env env)
{
    napi_value result;
    napi_get_null(env, &result);
    return result;
}

napi_value createNapiBool(napi_env env, double resultDoubleValue)
{
    napi_value result;
    napi_value resultBool;
    napi_create_double(env, resultDoubleValue, &result);
    napi_coerce_to_bool(env, result, &resultBool);
    return resultBool;
}

static napi_value onExit(napi_env env, napi_callback_info info)
{
    LOG("mmkv native onExit");
    MMKV::onExit();
    return createNapiUndefined(env);
}

static napi_value jniInitialize(napi_env env, napi_callback_info info)
{
    LOG("mmkv native jniInitialize");
    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string rootDir = getStringFromNapi(env, args[0]);

    string cacheDir = getStringFromNapi(env, args[1]);

    double logLevel = 0;
    napi_get_value_double(env, args[POSITION_TWO], &logLevel);

    if (rootDir.length() > 0) {
        MMKV::initializeMMKV(rootDir, (MMKVLogLevel)logLevel);
        g_android_tmpDir = cacheDir;
    }

    return createNapiUndefined(env);
}

static napi_value getDefaultMMKV(napi_env env, napi_callback_info info)
{
    LOG("mmkv native getDefaultMMKV");
    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double mode = 0;
    napi_get_value_double(env, args[0], &mode);

    string crypt = getStringFromNapi(env, args[1]);

    MMKV * kv = nullptr;

    if (crypt.length() > 0) {
        kv = MMKV::defaultMMKV((MMKVMode)mode, &crypt);
    }

    if (!kv) {
        kv = MMKV::defaultMMKV((MMKVMode)mode, nullptr);
    }

    double doubleKv = (double)((intptr_t)kv);
    return createNapiDouble(env, doubleKv);
}

static napi_value pageSize(napi_env env, napi_callback_info info)
{
    LOG("mmkv native pageSize");

    double size = (double)(DEFAULT_MMAP_SIZE);

    return createNapiDouble(env, size);
}

static napi_value version(napi_env env, napi_callback_info info)
{
    LOG("mmkv native version");

    string version = (string)(MMKV_VERSION);

    return createNapiString(env, version);
}

static napi_value isFileValid(napi_env env, napi_callback_info info)
{
    LOG("mmkv native isFileValid");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string mmapID = getStringFromNapi(env, args[0]);

    string root = getStringFromNapi(env, args[1]);

    bool resultBoolValue = false;
    double resultDoubleValue = 0;

    if (mmapID.length() > 0) {
        if (root.length() <= 0) {
            resultBoolValue = MMKV::isFileValid(mmapID, nullptr);
        } else {
            resultBoolValue = MMKV::isFileValid(mmapID, &root);
        }
        resultDoubleValue = static_cast<double>(resultBoolValue);
    }

    return createNapiBool(env, resultDoubleValue);
}

static napi_value getMMKVWithID(napi_env env, napi_callback_info info)
{
    LOG("mmkv native getMMKVWithID");

    size_t argc = 4;
    napi_value args[4] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string mmapID = getStringFromNapi(env, args[0]);

    double mode = 0;
    napi_get_value_double(env, args[1], &mode);

    string crypt = getStringFromNapi(env, args[2]);

    string root = getStringFromNapi(env, args[3]);

    MMKV * kv = nullptr;
    double doubleKv;

    if (mmapID.length() <= 0) {
        doubleKv = (double)((intptr_t)kv);
        return createNapiDouble(env, doubleKv);
    }

    bool done = false;
    if (crypt.length() > 0) {
        if (root.length() > 0) {
            kv = MMKV::mmkvWithID(mmapID, DEFAULT_MMAP_SIZE, (MMKVMode)mode, &crypt, &root);
        } else {
            kv = MMKV::mmkvWithID(mmapID, DEFAULT_MMAP_SIZE, (MMKVMode)mode, &crypt, nullptr);
        }
        done = true;
    }

    if (!done) {
        if (root.length() > 0) {
            kv = MMKV::mmkvWithID(mmapID, DEFAULT_MMAP_SIZE, (MMKVMode)mode, nullptr, &root);
        } else {
            kv = MMKV::mmkvWithID(mmapID, DEFAULT_MMAP_SIZE, (MMKVMode)mode, nullptr, nullptr);
        }
    }

    doubleKv = (double)((intptr_t)kv);
    return createNapiDouble(env, doubleKv);
}

static napi_value getMMKVWithAshmemFD(napi_env env, napi_callback_info info)
{
    LOG("mmkv native getMMKVWithID");

    size_t argc = 4;
    napi_value args[4] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string mmapID = getStringFromNapi(env, args[0]);

    double fd = 0;
    napi_get_value_double(env, args[1], &fd);

    double metaFD = 0;
    napi_get_value_double(env, args[POSITION_TWO], &metaFD);

    string crypt = getStringFromNapi(env, args[POSITION_THREE]);

    MMKV * kv = nullptr;
    double doubleKv;

    if (mmapID.length() <= 0 || fd < 0 || metaFD < 0) {
        doubleKv = (double)((intptr_t)kv);
        return createNapiDouble(env, doubleKv);
    }

    if (crypt.length() > 0) {
        kv = MMKV::mmkvWithAshmemFD(mmapID, fd, metaFD, &crypt);
    }
    if (!kv) {
        kv = MMKV::mmkvWithAshmemFD(mmapID, fd, metaFD, nullptr);
    }

    doubleKv = (double)((intptr_t)kv);
    return createNapiDouble(env, doubleKv);
}

static napi_value getMMKVWithIDAndSize(napi_env env, napi_callback_info info)
{
    LOG("mmkv native getMMKVWithIDAndSize");

    size_t argc = 4;
    napi_value args[4] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string mmapID = getStringFromNapi(env, args[0]);

    double size = 0;
    napi_get_value_double(env, args[1], &size);

    double mode = 0;
    napi_get_value_double(env, args[POSITION_TWO], &mode);

    string crypt = getStringFromNapi(env, args[POSITION_THREE]);

    MMKV * kv = nullptr;
    double doubleKv;

    if (mmapID.length() <= 0 || size < 0) {
        doubleKv = (double)((intptr_t)kv);
        return createNapiDouble(env, doubleKv);
    }

    if (crypt.length() > 0) {
        kv = MMKV::mmkvWithID(mmapID, size, (MMKVMode)mode, &crypt);
    }
    if (!kv) {
        kv = MMKV::mmkvWithID(mmapID, size, (MMKVMode)mode, nullptr);
    }
    doubleKv = (double)((intptr_t)kv);
    return createNapiDouble(env, doubleKv);
}

static napi_value encodeBool(napi_env env, napi_callback_info info)
{
    LOG("mmkv native encodeBool");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    bool value = 0;
    napi_get_value_bool(env, args[POSITION_TWO], &value);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        resultDoubleValue = (double)(kv->set(value, oKey));
    }

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value decodeBool(napi_env env, napi_callback_info info)
{
    LOG("mmkv native decodeBool");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    bool value = 0;
    napi_get_value_bool(env, args[POSITION_TWO], &value);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        resultDoubleValue = (double)(kv->getBool(oKey, value));
    }
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value encodeDouble(napi_env env, napi_callback_info info)
{
    LOG("mmkv native encodeDouble");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    double value = 0;
    napi_get_value_double(env, args[POSITION_TWO], &value);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        resultDoubleValue = (double)(kv->set(value, oKey));
    }
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value decodeDouble(napi_env env, napi_callback_info info)
{
    LOG("mmkv native decodeDouble");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    double value = 0;
    napi_get_value_double(env, args[POSITION_TWO], &value);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        resultDoubleValue = (double)(kv->getDouble(oKey, value));
    }
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value encodeString(napi_env env, napi_callback_info info)
{
    LOG("mmkv native encodeString");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    string value = getStringFromNapi(env, args[2]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        if (value.length() > 0) {
            resultDoubleValue = (double)(kv->set(value, oKey));
        } else {
            kv->removeValueForKey(oKey);
            resultDoubleValue = 1;
        }
    }

    return createNapiBool(env, resultDoubleValue);
}

static napi_value decodeString(napi_env env, napi_callback_info info)
{
    LOG("mmkv native decodeString");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    string oDefaultValue = getStringFromNapi(env, args[2]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    string value;

    if (kv && oKey.length() > 0) {
        bool hasValue = kv->getString(oKey, value);
        if (hasValue) {
            oDefaultValue = value;
        }
    }

    return createNapiString(env, oDefaultValue);
}

static napi_value containsKey(napi_env env, napi_callback_info info)
{
    LOG("mmkv native containsKey");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        resultDoubleValue = (double)(kv->containsKey(oKey));
    }

    return createNapiBool(env, resultDoubleValue);
}

static napi_value count(napi_env env, napi_callback_info info)
{
    LOG("mmkv native count");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv) {
        resultDoubleValue = (double)(kv->count());
    }
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value totalSize(napi_env env, napi_callback_info info)
{
    LOG("mmkv native totalSize");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv) {
        resultDoubleValue = (double)(kv->totalSize());
    }
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value actualSize(napi_env env, napi_callback_info info)
{
    LOG("mmkv native actualSize");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv) {
        resultDoubleValue = (double)(kv->actualSize());
    }

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value removeValueForKey(napi_env env, napi_callback_info info)
{
    LOG("mmkv native removeValueForKey");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv && oKey.length() > 0) {
        kv->removeValueForKey(oKey);
    }

    return createNapiUndefined(env);
}

static napi_value valueSize(napi_env env, napi_callback_info info)
{
    LOG("mmkv native valueSize");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    bool actualSize = 0;
    napi_get_value_bool(env, args[POSITION_TWO], &actualSize);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = 0;

    if (kv && oKey.length() > 0) {
        resultDoubleValue = (double)(kv->getValueSize(oKey, actualSize));
    }

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value setLogLevel(napi_env env, napi_callback_info info)
{
    LOG("mmkv native setLogLevel");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double level = 0;
    napi_get_value_double(env, args[0], &level);

    MMKV::setLogLevel((MMKVLogLevel)level);

    return createNapiUndefined(env);
}

static napi_value createNB(napi_env env, napi_callback_info info)
{
    LOG("mmkv native createNB");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double size = 0;
    napi_get_value_double(env, args[0], &size);

    double resultDoubleValue = 0;

    auto ptr = malloc(static_cast<size_t>(size));
    if (!ptr) {
        MMKVError("fail to create NativeBuffer:%s", strerror(errno));
        return createNapiDouble(env, resultDoubleValue);
    }
    resultDoubleValue = (double)(reinterpret_cast < long > (ptr));
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value destroyNB(napi_env env, napi_callback_info info)
{
    LOG("mmkv native destroyNB");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double pointer = 0;
    napi_get_value_double(env, args[0], &pointer);

    double size = 0;
    napi_get_value_double(env, args[1], &size);

    free(reinterpret_cast<void*>(static_cast<long>(pointer)));

    return createNapiUndefined(env);
}

static napi_value writeValueToNB(napi_env env, napi_callback_info info)
{
    LOG("mmkv native writeValueToNB");

    size_t argc = 4;
    napi_value args[4] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string oKey = getStringFromNapi(env, args[1]);

    double pointer = 0;
    napi_get_value_double(env, args[POSITION_TWO], &pointer);

    double size = 0;
    napi_get_value_double(env, args[POSITION_THREE], &size);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = -1;

    if (kv && oKey.length()) {
        resultDoubleValue = kv->writeValueToBuffer(oKey, reinterpret_cast < void *>((long)pointer), size);
    }

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value checkProcessMode(napi_env env, napi_callback_info info)
{
    LOG("mmkv native checkProcessMode");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV
    *>(hand);

    double resultDoubleValue = 0;

    if (kv) {
        resultDoubleValue = (double)(kv->checkProcessMode());
    }

    return createNapiBool(env, resultDoubleValue);
}

static napi_value backupOneToDirectory(napi_env env, napi_callback_info info)
{
    LOG("mmkv native backupOneToDirectory");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string mmapID = getStringFromNapi(env, args[0]);

    string dstDir = getStringFromNapi(env, args[1]);

    string rootPath = getStringFromNapi(env, args[2]);

    double resultDoubleValue = 0;

    if (rootPath.length() > 0) {
        resultDoubleValue = (double)(MMKV::backupOneToDirectory(mmapID, dstDir, &rootPath));
    } else {
        resultDoubleValue = (double)(MMKV::backupOneToDirectory(mmapID, dstDir));
    }

    return createNapiBool(env, resultDoubleValue);
}

static napi_value restoreOneMMKVFromDirectory(napi_env env, napi_callback_info info)
{
    LOG("mmkv native restoreOneMMKVFromDirectory");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string mmapID = getStringFromNapi(env, args[0]);

    string srcDir = getStringFromNapi(env, args[1]);

    string rootPath = getStringFromNapi(env, args[2]);

    double resultDoubleValue = 0;

    if (rootPath.length() > 0) {
        resultDoubleValue = (double)(MMKV::restoreOneFromDirectory(mmapID, srcDir, &rootPath));
    } else {
        resultDoubleValue = (double)(MMKV::restoreOneFromDirectory(mmapID, srcDir));
    }

    return createNapiBool(env, resultDoubleValue);
}

static napi_value backupAllToDirectory(napi_env env, napi_callback_info info)
{
    LOG("mmkv native backupAllToDirectory");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string dstDir = getStringFromNapi(env, args[0]);

    double resultDoubleValue = 0;

    resultDoubleValue = (double)(MMKV::backupAllToDirectory(dstDir));
    return createNapiDouble(env, resultDoubleValue);
}

static napi_value restoreAllFromDirectory(napi_env env, napi_callback_info info)
{
    LOG("mmkv native restoreAllFromDirectory");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    string srcDir = getStringFromNapi(env, args[0]);

    double resultDoubleValue = 0;

    resultDoubleValue = (double)(MMKV::restoreAllFromDirectory(srcDir));

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value cryptKey(napi_env env, napi_callback_info info)
{
    LOG("mmkv native cryptKey");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);
    string cryptKey;
    if (kv) {
        cryptKey = kv->cryptKey();
    }

    return createNapiString(env, cryptKey);
}

static napi_value reKey(napi_env env, napi_callback_info info)
{
    LOG("mmkv native reKey");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string newKey = getStringFromNapi(env, args[1]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);
    bool reKeyIsOk;
    if (kv) {
        reKeyIsOk = kv->reKey(newKey);
    }

    double resultDoubleValue = 0;

    resultDoubleValue = reKeyIsOk;

    return createNapiBool(env, resultDoubleValue);
}

static napi_value checkReSetCryptKey(napi_env env, napi_callback_info info)
{
    LOG("mmkv native checkReSetCryptKey");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string newKey = getStringFromNapi(env, args[1]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        if (newKey.empty()) {
            kv->checkReSetCryptKey(nullptr);
        } else {
            kv->checkReSetCryptKey(&newKey);
        }
    }

    return createNapiUndefined(env);
}

static napi_value mmapID(napi_env env, napi_callback_info info)
{
    LOG("mmkv native mmapID");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    string mmapId;

    if (kv) {
        mmapId = kv->mmapID();
    }

    return createNapiString(env, mmapId);
}

static napi_value clearAll(napi_env env, napi_callback_info info)
{
    LOG("mmkv native clearAll");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        kv->clearAll();
    }

    return createNapiUndefined(env);
}

static napi_value trim(napi_env env, napi_callback_info info)
{
    LOG("mmkv native trim");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        kv->trim();
    }

    return createNapiUndefined(env);
}

static napi_value close(napi_env env, napi_callback_info info)
{
    LOG("mmkv native close");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        kv->close();
    }

    return createNapiUndefined(env);
}

static napi_value clearMemoryCache(napi_env env, napi_callback_info info)
{
    LOG("mmkv native clearMemoryCache");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        kv->clearMemoryCache();
    }

    return createNapiUndefined(env);
}

static napi_value encodeSet(napi_env env, napi_callback_info info)
{
    LOG("mmkv native encodeSet");

    size_t argc = 3;
    napi_value args[3] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string key = getStringFromNapi(env, args[1]);

    vector < string > value = getVectorFromNapi(env, args[POSITION_TWO]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    bool resultBooleanValue = false;
    double resultDoubleValue = 0;

    if (value.size() > 0) {
        resultBooleanValue = kv->set(value, key);
    } else {
        kv->removeValueForKey(key);
        resultBooleanValue = true;
    }

    resultDoubleValue = static_cast<double>(resultBooleanValue);
    return createNapiBool(env, resultDoubleValue);
}

static napi_value decodeStringSet(napi_env env, napi_callback_info info)
{
    LOG("mmkv native decodeStringSet");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    string key = getStringFromNapi(env, args[1]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv && key.length() > 0) {
        vector < string > value;
        bool hasValue = kv->getVector(key, value);
        if (hasValue) {
            return createNapiArray(env, value);
        }
    }

    return createNapiNull(env);
}

static napi_value removeValuesForKeys(napi_env env, napi_callback_info info)
{
    LOG("mmkv native removeValuesForKeys");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    vector < string > value = getVectorFromNapi(env, args[1]);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv && !value.empty()) {
        kv->removeValuesForKeys(value);
    }

    return createNapiUndefined(env);
}

static napi_value allKeys(napi_env env, napi_callback_info info)
{
    LOG("mmkv native allKeys");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        vector < string > value = kv->allKeys();
        return createNapiArray(env, value);
    }

    return createNapiNull(env);
}

static napi_value checkContentChangedByOuterProcess(napi_env env, napi_callback_info info)
{
    LOG("mmkv native checkContentChangedByOuterProcess");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    if (kv) {
        kv->checkContentChanged();
    }

    return createNapiUndefined(env);
}

static napi_value lock(napi_env env, napi_callback_info info)
{
    LOG("mmkv native lock");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);


    if (kv) {
        kv->lock();
    }

    return createNapiUndefined(env);
}

static napi_value unlock(napi_env env, napi_callback_info info)
{
    LOG("mmkv native unlock");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);


    if (kv) {
        kv->unlock();
    }

    return createNapiUndefined(env);
}

static napi_value tryLock(napi_env env, napi_callback_info info)
{
    LOG("mmkv native tryLock");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    bool resultBoolValue = false;
    double resultDoubleValue = 0;
    if (kv) {
        resultBoolValue = kv->try_lock();
    }
    resultDoubleValue = static_cast<double>(resultBoolValue);
    return createNapiBool(env, resultDoubleValue);
}

static napi_value sync(napi_env env, napi_callback_info info)
{
    LOG("mmkv native sync");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    bool sync = false;
    napi_get_value_bool(env, args[0], &sync);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);


    if (kv) {
        kv->sync((SyncFlag)sync);
    }

    return createNapiUndefined(env);
}

static napi_value ashmemFD(napi_env env, napi_callback_info info)
{
    LOG("mmkv native ashmemFD");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast < MMKV*>(hand);

    double resultDoubleValue = -1;
    if (kv) {
        resultDoubleValue = (double)kv->ashmemFD();
    }

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value ashmemMetaFD(napi_env env, napi_callback_info info)
{
    LOG("mmkv native ashmemMetaFD");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    double handle = 0;
    napi_get_value_double(env, args[0], &handle);

    long hand = static_cast<long>(handle);
    MMKV * kv = reinterpret_cast<MMKV*>(hand);

    double resultDoubleValue = -1;
    if (kv) {
        resultDoubleValue = (double)kv->ashmemMetaFD();
    }

    return createNapiDouble(env, resultDoubleValue);
}

static napi_value setFun(napi_env env, napi_callback_info info)
{
    size_t argc = 4;
    napi_value args[4] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (jsFunChangedByOuterProcess == nullptr) {
        napi_create_reference(env, args[0], 1, &jsFunChangedByOuterProcess);
    }
    if (jsFunMMKVLogImp == nullptr) {
        napi_create_reference(env, args[1], 1, &jsFunMMKVLogImp);
    }
    if (jsFunMMKVCRCCheckFail == nullptr) {
        napi_create_reference(env, args[POSITION_TWO], 1, &jsFunMMKVCRCCheckFail);
    }
    if (jsFunMMKVFileLengthError == nullptr) {
        napi_create_reference(env, args[POSITION_THREE], 1, &jsFunMMKVFileLengthError);
    }

    return createNapiUndefined(env);
}

static void OnContentChangedByOuterProcess(const std::string & mmapID)
{
    if (jsFunChangedByOuterProcess != nullptr) {
        napi_value str = createNapiString(napiEnv, mmapID);
        napi_value callback = nullptr;
        napi_value result;
        napi_get_reference_value(napiEnv, jsFunChangedByOuterProcess, &callback);
        napi_call_function(napiEnv, nullptr, callback, 1, &str, &result);
    }
}

static napi_value setWantsContentChangeNotify(napi_env env, napi_callback_info info)
{
    LOG("mmkv native setWantsContentChangeNotify");

    size_t argc = 1;
    napi_value args[1] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    bool notify = 0;
    napi_get_value_bool(env, args[0], &notify);

    if (notify) {
        MMKV::registerContentChangeHandler(OnContentChangedByOuterProcess);
    } else {
        MMKV::unRegisterContentChangeHandler();
    }

    return createNapiUndefined(env);
}

MMKVRecoverStrategic onMMKVError(const std::string & mmapID, MMKVErrorType errorType)
{
    napi_value str = createNapiString(napiEnv, mmapID);
    napi_value callback = nullptr;
    napi_value result;
    if (errorType == MMKVCRCCheckFail) {
        napi_get_reference_value(napiEnv, jsFunMMKVCRCCheckFail, &callback);
        napi_call_function(napiEnv, nullptr, callback, 1, &str, &result);
        return OnErrorRecover;
    } else if (errorType == MMKVFileLength) {
        napi_get_reference_value(napiEnv, jsFunMMKVFileLengthError, &callback);
        napi_call_function(napiEnv, nullptr, callback, 1, &str, &result);
        return OnErrorRecover;
    }

    return OnErrorDiscard;
}

static void mmkvLog(MMKVLogLevel level, const char * file, int line, const char * function, const std::string & message)
{
    if (jsFunMMKVLogImp != nullptr) {
        napi_value array[4] = { 0 };
        array[0] = createNapiString(napiEnv, string(file));
        array[1] = createNapiString(napiEnv, string(function));
        array[POSITION_TWO] = createNapiString(napiEnv, message);
        array[POSITION_THREE] = createNapiDouble(napiEnv, level);

        napi_value callback = nullptr;
        napi_value result;
        napi_get_reference_value(napiEnv, jsFunMMKVLogImp, &callback);
        int arraySize = 4;
        napi_call_function(napiEnv, nullptr, callback, arraySize, array, &result);
    }
}

static napi_value setCallbackHandler(napi_env env, napi_callback_info info)
{
    LOG("mmkv native setCallbackHandler");

    size_t argc = 2;
    napi_value args[2] = { nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    bool logReDirecting = 0;
    napi_get_value_bool(env, args[0], &logReDirecting);

    bool hasCallback = 1;
    napi_get_value_bool(env, args[1], &hasCallback);

    if (logReDirecting) {
        MMKV::registerLogHandler(mmkvLog);
    } else {
        MMKV::unRegisterLogHandler();
    }

    if (hasCallback) {
        MMKV::registerErrorHandler(onMMKVError);
    } else {
        MMKV::unRegisterErrorHandler();
    }

    return createNapiUndefined(env);
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "onExit", nullptr, onExit, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getDefaultMMKV", nullptr, getDefaultMMKV, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "jniInitialize", nullptr, jniInitialize, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "pageSize", nullptr, pageSize, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "version", nullptr, version, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "isFileValid", nullptr, isFileValid, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getMMKVWithID", nullptr, getMMKVWithID, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getMMKVWithIDAndSize", nullptr, getMMKVWithIDAndSize, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "getMMKVWithAshmemFD", nullptr, getMMKVWithAshmemFD, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "encodeBool", nullptr, encodeBool, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "decodeBool", nullptr, decodeBool, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "encodeDouble", nullptr, encodeDouble, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "decodeDouble", nullptr, decodeDouble, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "encodeString", nullptr, encodeString, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "decodeString", nullptr, decodeString, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "containsKey", nullptr, containsKey, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "count", nullptr, count, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "totalSize", nullptr, totalSize, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "actualSize", nullptr, actualSize, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "removeValueForKey", nullptr, removeValueForKey, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "setLogLevel", nullptr, setLogLevel, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "createNB", nullptr, createNB, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "destroyNB", nullptr, destroyNB, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "writeValueToNB", nullptr, writeValueToNB, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "checkProcessMode", nullptr, checkProcessMode, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "backupOneToDirectory", nullptr, backupOneToDirectory, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "restoreOneMMKVFromDirectory", nullptr, restoreOneMMKVFromDirectory, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "backupAllToDirectory", nullptr, backupAllToDirectory, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "restoreAllFromDirectory", nullptr, restoreAllFromDirectory, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "cryptKey", nullptr, cryptKey, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "reKey", nullptr, reKey, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "checkReSetCryptKey", nullptr, checkReSetCryptKey, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "mmapID", nullptr, mmapID, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "allKeys", nullptr, allKeys, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "clearAll", nullptr, clearAll, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "trim", nullptr, trim, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "close", nullptr, close, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "clearMemoryCache", nullptr, clearMemoryCache, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "checkContentChangedByOuterProcess", nullptr, checkContentChangedByOuterProcess, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "encodeSet", nullptr, encodeSet, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "decodeStringSet", nullptr, decodeStringSet, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "removeValuesForKeys", nullptr, removeValuesForKeys, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "lock", nullptr, lock, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "unlock", nullptr, unlock, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "tryLock", nullptr, tryLock, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "sync", nullptr, sync, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "ashmemFD", nullptr, ashmemFD, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "ashmemMetaFD", nullptr, ashmemMetaFD, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "setFun", nullptr, setFun, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "setCallbackHandler", nullptr, setCallbackHandler, nullptr, nullptr, nullptr, napi_default, nullptr },
        { "setWantsContentChangeNotify", nullptr, setWantsContentChangeNotify, nullptr, nullptr, nullptr, napi_default, nullptr }
         };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);

    napiEnv = env;
    return exports;
}
EXTERN_C_END


/*
 * Module define
 */
static napi_module
demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = Init,
.nm_modname = "mmkv",
.nm_priv = ((void *)0),
.reserved = {
0 },
};

/*
 * Module register function
 */
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
napi_module_register(& demoModule);
}
